001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.tools.impl;
020    
021    import java.io.File;
022    import java.net.URI;
023    import java.net.URL;
024    import java.util.Vector;
025    
026    import net.dpml.library.Builder;
027    import net.dpml.library.Library;
028    import net.dpml.library.Resource;
029    
030    import net.dpml.tools.Context;
031    import net.dpml.tools.info.BuilderDirective;
032    import net.dpml.tools.info.BuilderDirectiveHelper;
033    
034    import net.dpml.transit.Artifact;
035    import net.dpml.transit.model.TransitModel;
036    import net.dpml.transit.tools.MainTask;
037    
038    import net.dpml.util.Logger;
039    
040    import org.apache.tools.ant.Project;
041    import org.apache.tools.ant.ProjectHelper;
042    import org.apache.tools.ant.BuildException;
043    import org.apache.tools.ant.BuildLogger;
044    import org.apache.tools.ant.DefaultLogger;
045    import org.apache.tools.ant.input.DefaultInputHandler;
046    import org.apache.tools.ant.DemuxInputStream;
047    
048    /**
049     * The StandardBuilder is a plugin established by the Tools build controller
050     * used for the building of a project based on the Ant build system in conjunction
051     * with Transit plugin management services.
052     *
053     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
054     * @version 1.1.0
055     */
056    public class StandardBuilder implements Builder 
057    {
058        // ------------------------------------------------------------------------
059        // static
060        // ------------------------------------------------------------------------
061    
062       /**
063        * The default template uri path.
064        */
065        public static final String DEFAULT_TEMPLATE_URN = "local:template:dpml/tools/standard";
066        
067       /**
068        * The builder configuration.
069        */
070        public static final BuilderDirective CONFIGURATION = loadConfiguration();
071        
072        private static BuilderDirective loadConfiguration()
073        {
074            try
075            {
076                return BuilderDirectiveHelper.build();
077            }
078            catch( Throwable e )
079            {
080                final String error = 
081                  "Internal error while attempting to establish the builder configuration.";
082                BuilderError be = new BuilderError( error, e );
083                be.printStackTrace();
084                return null;
085            }
086        }
087        
088        // ------------------------------------------------------------------------
089        // state
090        // ------------------------------------------------------------------------
091        
092        private Logger m_logger;
093        private TransitModel m_model;
094        private Library m_library;
095        private boolean m_verbose;
096        private Throwable m_result;
097    
098        // ------------------------------------------------------------------------
099        // constructors
100        // ------------------------------------------------------------------------
101    
102       /**
103        * Creation of a new standard builder.
104        *
105        * @param logger assigned logging channel
106        * @param library the library
107        */
108        public StandardBuilder( Logger logger, Library library )
109        {
110            this( logger, library, false );
111        }
112        
113       /**
114        * Creation of a new standard builder.
115        *
116        * @param logger assigned logging channel
117        * @param library the library
118        * @param verbose verbose execution flag
119        */
120        public StandardBuilder( Logger logger, Library library, boolean verbose )
121        {
122            m_logger = logger;
123            m_verbose = verbose;
124            m_library = library;
125            
126            Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
127        }
128    
129        // ------------------------------------------------------------------------
130        // Builder
131        // ------------------------------------------------------------------------
132    
133       /**
134        * Build the project defined by the supplied resource.
135        * @param resource the project definition
136        * @param targets an array of build target names
137        * @return the build success status
138        */
139        public boolean build( Resource resource, String[] targets )
140        {
141            Project project = createProject( resource );
142            File template = getTemplateFile( resource );
143            return build( resource, project, template, targets );
144        }
145        
146       /**
147        * Return the template for the resource.
148        * @param resource the project definition
149        * @return the template
150        */
151        public File getTemplateFile( Resource resource )
152        {
153            try
154            {
155                String systemOverride = System.getProperty( "project.template" );
156                String override = resource.getProperty( "project.template", systemOverride );
157                if( null != override )
158                {
159                    File template = getTemplateFile( override );
160                    return template;
161                }
162                
163                File basedir = resource.getBaseDir();
164                String buildfile = resource.getProperty( "project.buildfile" );
165                String defaultBuildfile = resource.getProperty( "project.standard.buildfile", "build.xml" );
166                if( null != buildfile )
167                {
168                    // there is an explicit 'project.buildfile' declaration in which case
169                    // we check for existance and fail if it does not exist
170                    
171                    File file = new File( basedir, buildfile );
172                    if( file.exists() )
173                    {
174                        return file;
175                    }
176                    else
177                    {
178                        final String error = 
179                          "Resource buildfile ["
180                          + file
181                          + "] does not exist.";
182                        throw new BuildException( error );
183                    }
184                }
185                else if( null != defaultBuildfile )
186                {
187                    // check if a buildfile of the default name exists in the project's 
188                    // basedir - and if so - use it to build the project
189                    
190                    File file = new File( basedir, defaultBuildfile );
191                    if( file.exists() )
192                    {
193                        return file;
194                    }
195                }
196                
197                // otherwise we build using either an explicit or default template
198                // resolved via a uri (typically a template stored in prefs)
199                
200                String defaultTemplateSpec = 
201                  resource.getProperty( "project.standard.template", DEFAULT_TEMPLATE_URN );
202                String templateSpec = resource.getProperty( "project.template", defaultTemplateSpec );
203                    
204                if( null != templateSpec )
205                {
206                    File template = getTemplateFile( templateSpec );
207                    return template;
208                }
209                else
210                {
211                    final String error = 
212                      "Resource template property 'project.template' is undefined.";
213                    throw new BuildException( error );
214                }
215            }
216            catch( BuildException e )
217            {
218                throw e;
219            }
220            catch( Throwable e )
221            {
222                m_result = e;
223                final String error = 
224                  "Unexpected error while attempting to resolve project template."
225                  + "\nResource path: " 
226                  + resource.getResourcePath();
227                throw new BuildException( error, e );
228            }
229        }
230        
231        // ------------------------------------------------------------------------
232        // implementation
233        // ------------------------------------------------------------------------
234        
235        boolean build( Resource resource, Project project, File template, String[] targets )
236        {
237            try
238            {
239                ProjectHelper helper = (ProjectHelper) project.getReference( "ant.projectHelper" );
240                
241                if( null != template )
242                {
243                    helper.parse( project, template );
244                }
245                
246                Vector vector = new Vector();
247                
248                if( targets.length == 0 )
249                {
250                    if( null != project.getDefaultTarget() )
251                    {
252                        vector.addElement( project.getDefaultTarget() );
253                    }
254                    else
255                    {
256                        vector.addElement( CONFIGURATION.getDefaultPhase() );
257                    }
258                }
259                else
260                {
261                    for( int i=0; i<targets.length; i++ )
262                    {
263                        String target = targets[i];
264                        vector.addElement( target );
265                    }
266                }
267                
268                if( vector.size() == 0 )
269                {
270                    final String errorMessage =
271                      "No targets requested and no default target declared.";
272                    throw new BuildException( errorMessage );
273                }
274                
275                project.executeTargets( vector );
276                return true;
277            }
278            catch( BuildException e )
279            {
280                m_result = e;
281                if( m_logger.isDebugEnabled() )
282                {
283                    final String error = 
284                      "Build failure."
285                      + "\nProject: " + resource.getResourcePath()
286                      + "\nBasedir: " + resource.getBaseDir()
287                      + "\nTemplate: " + template 
288                      + "\nLocation: " + e.getLocation();
289                    Throwable cause = e.getCause();
290                    m_logger.error( error, cause );
291                }
292                return false;
293            }
294            finally
295            {
296                project.fireBuildFinished( m_result );
297            }
298        }
299        
300        private File getTemplateFile( String spec )
301        {
302            try
303            {
304                URI uri = new URI( spec );
305                if( Artifact.isRecognized( uri ) )
306                {
307                    URL url = uri.toURL();
308                    return (File) url.getContent( new Class[]{File.class} );
309                }
310            }
311            catch( Throwable e )
312            {
313            }
314            return new File( spec );
315        }
316        
317        Project createProject( Resource resource )
318        {
319            try
320            {
321                Project project = newProject();
322                Context context = new DefaultContext( resource, project );
323                return project;
324            }
325            catch( Exception e )
326            {
327                final String error = 
328                  "Unable to establish build context." 
329                  + "\nProject: " + resource;
330                throw new BuildException( error, e );
331            }
332        }
333        
334        private Project newProject() 
335        {
336            Project project = new Project();
337            project.setSystemProperties();
338            project.setDefaultInputStream( System.in );
339            setupTransitComponentHelper( project );
340            project.setCoreLoader( getClass().getClassLoader() );
341            project.addBuildListener( createLogger() );
342            System.setIn( new DemuxInputStream( project ) );
343            project.setProjectReference( new DefaultInputHandler() );
344            ProjectHelper helper = ProjectHelper.getProjectHelper();
345            project.addReference( "ant.projectHelper", helper );
346            return project;
347        }
348        
349        private void setupTransitComponentHelper( Project project ) 
350        {
351            try
352            {
353                MainTask task = new MainTask();
354                task.setProject( project );
355                task.init();
356                task.execute();
357            }
358            catch( BuildException e )
359            {
360                throw e;
361            }
362            catch( Exception e )
363            {
364                final String error = 
365                  "Setup failure.";
366                throw new BuildException( error, e );
367            }
368        }
369        
370        private BuildLogger createLogger()
371        {
372            BuildLogger logger = new DefaultLogger();
373            if( m_verbose )
374            {
375                logger.setMessageOutputLevel( Project.MSG_VERBOSE );
376            }
377            else
378            {
379                logger.setMessageOutputLevel( Project.MSG_INFO );
380            }
381            logger.setOutputPrintStream( System.out );
382            logger.setErrorPrintStream( System.err );
383            return logger;
384        }
385        
386        private Logger getLogger()
387        {
388            return m_logger;
389        }
390    }
391